package org.korosoft.findbugs.aggregator;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

public final class Formatter {
    private static final Log LOG = LogFactory.getLog(Formatter.class);

    Configuration cfg;

    public Formatter() {
        cfg = new Configuration();
    }

    public void formatPages(File outputDir, File templateDir, BugCollector collector) throws IOException {

        cfg.setDirectoryForTemplateLoading(new File(templateDir, "dynamic"));
        cfg.setObjectWrapper(new DefaultObjectWrapper());

        FileUtils.copyDirectory(new File(templateDir, "static"), outputDir);

        File sourcesDir = new File(outputDir, "sources");
        if (!sourcesDir.mkdirs()) {
            LOG.error("Failed to create directory " + sourcesDir.getAbsolutePath());
            return;
        }
        Map<File, List<Bug>> bugsBySourceFile = new HashMap<File, List<Bug>>();
        for (Bug bug : collector.getAllBugs()) {
            List<Bug> list = bugsBySourceFile.get(bug.getSourceFile());
            if (list == null) {
                list = new ArrayList<Bug>();
                bugsBySourceFile.put(bug.getSourceFile(), list);
            }
            list.add(bug);
        }

        // All source files
        LOG.info("Creating source files");
        List<File> sourceFiles = new ArrayList<File>(bugsBySourceFile.keySet());
        Map<File, String> filesMap = new HashMap<File, String>();
        for (int i = sourceFiles.size() - 1; i >= 0; i--) {
            File file = sourceFiles.get(i);
            String fileName = "source_file_" + Integer.toString(i) + ".html";
            filesMap.put(file, fileName);
            File sourceFile = new File(sourcesDir, fileName);
            LOG.debug("Writing source file " + sourceFile.getAbsolutePath());
            String fileContent = FileUtils.readFileToString(file, "UTF-8").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
            Map<String, Object> vars = new HashMap<String, Object>();
            vars.put("fileName", file.getAbsolutePath());
            vars.put("fileContent", fileContent);
            String prepared = fileContent.replaceAll("[\r\n]", "\n").replaceAll("\r", "\n").replaceAll("[^\n]", "");
            String lc = Integer.toString(prepared.length());
            vars.put("lineCount", lc);
            Writer out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(sourceFile, false)));
            Template template = cfg.getTemplate("source.ftl", "UTF-8");
            try {
                template.process(vars, out);
            } catch (TemplateException e) {
                LOG.error("Exception", e);
                throw new AggregatorException(e);
            }
            out.close();
        }

        // All bugs
        LOG.info("Creating bug files");
        File bugsDir = new File(outputDir, "bugs");
        if (!bugsDir.mkdirs()) {
            LOG.error("Failed to create directory " + bugsDir.getAbsolutePath());
            return;
        }
        List<Bug> bugs = new ArrayList<Bug>(collector.getAllBugs());
        for (int i = bugs.size() - 1; i >= 0; i--) {
            Bug bug = bugs.get(i);
            String fileName = "bug_" + Integer.toString(i) + ".html";
            bug.setTag(fileName);
            File bugFile = new File(bugsDir, fileName);
            LOG.debug("Writing bug file " + bugFile.getAbsolutePath());

            Map<String, Object> vars = new HashMap<String, Object>();
            vars.put("bug", bug);
            vars.put("source", filesMap.get(bug.getSourceFile()));
            Writer out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(bugFile, false)));
            Template template = cfg.getTemplate("bug.ftl", "UTF-8");
            try {
                template.process(vars, out);
            } catch (TemplateException e) {
                LOG.error("Exception", e);
                throw new AggregatorException(e);
            }
            out.close();
        }

        // Index page
        LOG.info("Creating index file");
        try {
            Template template = cfg.getTemplate("index.ftl", "UTF-8");
            File targetFile = new File(outputDir, "index.html");
            Writer out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(targetFile, false)));
            Map<String, Object> vars = new HashMap<String, Object>();
            vars.put("collector", collector);
            vars.put("priorities", new TreeSet<String>(collector.getByPriority().keySet()));
            vars.put("types", new TreeSet<String>(collector.getByType().keySet()));
            vars.put("categories", new TreeSet<String>(collector.getByCategory().keySet()));
            template.process(vars, out);
            out.close();
        } catch (TemplateException e) {
            LOG.error("Exception", e);
            throw new AggregatorException(e);
        }

        formatMaster(outputDir, collector, "priority");

        formatMaster(outputDir, collector, "category");

        formatMaster(outputDir, collector, "type");

    }

    private void formatMaster(File outputDir, BugCollector collector, String property) throws IOException {
        // Priority
        LOG.info("Creating pages split by " + property);
        try {
            File targetDir = new File(outputDir, property);
            if (!targetDir.mkdirs()) {
                LOG.error("Failed to create dir: " + targetDir);
                return;
            }
            Map<String, List<Bug>> map = null;
            if (property.equals("priority")) {
                map = collector.getByPriority();
            } else if (property.equals("category")) {
                map = collector.getByCategory();
            } else if (property.equals("type")) {
                map = collector.getByType();
            }
            if (map == null) {
                throw new IllegalArgumentException("property");
            }
            Set<String> keySet = map.keySet();
            for (String propValue : keySet) {
                Template template = cfg.getTemplate("master.ftl", "UTF-8");
                File targetFile = new File(targetDir, propValue + ".html");
                Writer out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(targetFile, false)));
                Map<String, Object> vars = new HashMap<String, Object>();
                vars.put(property, new TreeSet<String>(keySet));
                vars.put("splitName", StringUtils.capitalise(property));
                vars.put("splitValue", propValue);

                Map<String, List<Bug>> detail1 = new HashMap<String, List<Bug>>();
                Map<String, List<Bug>> detail2 = new HashMap<String, List<Bug>>();

                String detail1GetterName;
                String detail2GetterName;
                String detail1Name = property.equals("priority") ? "category" : "priority";
                String detail2Name = property.equals("type") ? "category" : "type";
                detail1GetterName = "get" + StringUtils.capitalise(detail1Name);
                detail2GetterName = "get" + StringUtils.capitalise(detail2Name);

                Method detailProp1 = Bug.class.getDeclaredMethod(detail1GetterName);
                Method detailProp2 = Bug.class.getDeclaredMethod(detail2GetterName);

                for (Bug bug : map.get(propValue)) {

                    if (detail1.get((String) detailProp1.invoke(bug)) == null) {
                        detail1.put((String) detailProp1.invoke(bug), new ArrayList<Bug>());
                    }
                    detail1.get((String) detailProp1.invoke(bug)).add(bug);

                    if (detail2.get((String) detailProp2.invoke(bug)) == null) {
                        detail2.put((String) detailProp2.invoke(bug), new ArrayList<Bug>());
                    }
                    detail2.get((String) detailProp2.invoke(bug)).add(bug);
                }
                vars.put("prop", property);
                vars.put("prop1", detail1Name);
                vars.put("prop2", detail2Name);
                vars.put("cprop1", StringUtils.capitalise(detail1Name));
                vars.put("cprop2", StringUtils.capitalise(detail2Name));
                vars.put("bugs", map.get(propValue));
                vars.put("props1", new TreeSet<String>(detail1.keySet()));
                vars.put("props2", new TreeSet<String>(detail2.keySet()));
                template.process(vars, out);
                out.close();

                formatBranchDetail(targetDir, property, propValue, detail1Name, detail1);

                formatBranchDetail(targetDir, property, propValue, detail2Name, detail2);

            }
        } catch (TemplateException e) {
            LOG.error("Exception", e);
            throw new AggregatorException(e);
        } catch (InvocationTargetException e) {
            LOG.error("Exception", e);
            throw new AggregatorException(e);
        } catch (NoSuchMethodException e) {
            LOG.error("Exception", e);
            throw new AggregatorException(e);
        } catch (IllegalAccessException e) {
            LOG.error("Exception", e);
            throw new AggregatorException(e);
        }
    }

    private void formatBranchDetail(File masterDir, String masterProperty, String masterPropertyValue, String property, Map<String, List<Bug>> bugs) throws IOException, TemplateException {
        File targetDir = new File(new File(masterDir, masterPropertyValue), property);
        if (!targetDir.mkdirs()) {
            LOG.error("Failed to create dir: " + targetDir);
            return;
        }
        try {
            for (String propValue : bugs.keySet()) {
                Template template = cfg.getTemplate("detail.ftl", "UTF-8");
                File targetFile = new File(targetDir, propValue + ".html");
                Writer out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(targetFile, false)));
                Map<String, Object> vars = new HashMap<String, Object>();
                vars.put("splitName", StringUtils.capitalise(masterProperty));
                vars.put("splitValue", masterPropertyValue);
                vars.put("detailName", StringUtils.capitalise(property));
                vars.put("detailValue", propValue);

                String detailName = "";
                if (masterProperty .equals("priority")) {
                    detailName = property.equals("type") ? "category" : "type";
                }
                if (masterProperty .equals("category")) {
                    detailName = property.equals("type") ? "priority" : "type";
                }
                if (masterProperty .equals("type")) {
                    detailName = property.equals("priority") ? "category" : "priority";
                }
                String detailGetterName = "get" + StringUtils.capitalise(detailName);
                Method detailGetter = Bug.class.getDeclaredMethod(detailGetterName);

                vars.put("prop", property);
                vars.put("propValue", propValue);
                vars.put("cprop", StringUtils.capitalise(property));
                vars.put("master", masterProperty);
                vars.put("cmaster", StringUtils.capitalise(masterProperty));
                vars.put("prop1", detailName);
                vars.put("cprop1", StringUtils.capitalise(detailName));
                Map<String, List<Bug>> detail = new HashMap<String, List<Bug>>();
                for (Bug bug : bugs.get(propValue)) {
                    if (detail.get((String) detailGetter.invoke(bug)) == null) {
                        detail.put((String) detailGetter.invoke(bug), new ArrayList<Bug>());
                    }
                    detail.get((String) detailGetter.invoke(bug)).add(bug);
                }
                vars.put("bugs", bugs.get(propValue));
                vars.put("detail", new TreeSet<String>(detail.keySet()));
                template.process(vars, out);
                out.close();

                formatBranchDeepDetail(targetDir, masterProperty, masterPropertyValue, property, propValue, detailName, detail);
            }
        } catch (NoSuchMethodException e) {
            LOG.error("Exception", e);
            throw new AggregatorException(e);
        } catch (IllegalAccessException e) {
            LOG.error("Exception", e);
            throw new AggregatorException(e);
        } catch (InvocationTargetException e) {
            LOG.error("Exception", e);
            throw new AggregatorException(e);
        }
    }

    private void formatBranchDeepDetail(File detailDir, String masterProperty, String masterPropertyValue, String detailProperty, String detailPropertyValue, String property, Map<String, List<Bug>> bugs) throws IOException, TemplateException {
        File targetDir = new File(new File(detailDir, detailPropertyValue), property);
        if (!targetDir.mkdirs()) {
            LOG.error("Failed to create dir: " + targetDir);
            return;
        }
        for (String propValue : bugs.keySet()) {
            Template template = cfg.getTemplate("deepDetail.ftl", "UTF-8");
            File targetFile = new File(targetDir, propValue + ".html");
            Writer out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(targetFile, false)));
            Map<String, Object> vars = new HashMap<String, Object>();

            vars.put(masterProperty, masterPropertyValue);
            vars.put(detailProperty, detailPropertyValue);
            vars.put(property, propValue);

            vars.put("bugs", bugs.get(propValue));
            try {
            template.process(vars, out);

            } catch (TemplateException e) {
                throw new AggregatorException(e);
            }
            out.close();
        }
    }
}
